﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Configuration;

namespace PolicySocketServices
{
    
    class PolicySocketServer
    {
        TcpListener _Listener = null;
        const string    _PolicyRequestString = "<policy-file-request/>";
        byte[] _Policy = null;
        uint _AcceptedCount = 0;
        uint _DisconnectedCount = 0;
        
        
        class Session
        {
            public int         _SendedLength { get; set; }
            public int         _ReceivedLength { get; set; }
            public byte[]      _ReceiveBuffer  { get; set; }
            public Socket       _Client  { get; set; }
            public Session()
            {
                _SendedLength = 0;
                _ReceivedLength = 0;
                _ReceiveBuffer = null;
                _Client = null;
            }
            public void Disconnect(System.AsyncCallback callback)
            {                
                _Client.BeginDisconnect(true, callback, this);
            }
            
        }

        private void InitializeData()
        {
            string policyFile = ConfigurationManager.AppSettings["PolicyFilePath"];
            using (FileStream fs = new FileStream(policyFile, FileMode.Open))
            {
                _Policy = new byte[fs.Length];
                fs.Read(_Policy, 0, _Policy.Length);
            }            
        }

        private void readyForAcceptingSocket()
        {
            Session session = new Session();
            session._ReceiveBuffer = new byte[_PolicyRequestString.Length];
            _Listener.BeginAcceptSocket(new AsyncCallback(OnBeginAccept), session);            
        }

        public void StartSocketServer()
        {
            InitializeData();

            try
            {
                string strPolicyPort = ConfigurationManager.AppSettings["PolicyServerPort"];
                short port = Convert.ToInt16(strPolicyPort);

                string strBacklog = ConfigurationManager.AppSettings["Backlog"];
                int backlog = Convert.ToInt32(strPolicyPort);

                ThreadPool.SetMaxThreads(backlog / 2, backlog);

                //Using TcpListener which is a wrapper around a Socket
                //Allowed port is 943 for Silverlight or 843 for Flash sockets policy data
                _Listener = new TcpListener(IPAddress.Any, port);
                _Listener.Start(backlog);
                Console.WriteLine("Policy server listening port {0}...", port);
                int i = 0;
                while (i < backlog)
                {                    
                    readyForAcceptingSocket();                   
                    ++i;                 
                }
            }
            catch (Exception exp)
            {
                LogError(exp);                
            }
        }

        private void OnBeginAccept(IAsyncResult ar)
        {            
            readyForAcceptingSocket();
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProcForAccept), ar);

            Console.WriteLine("OnAccept acceptedCount {0}", ++_AcceptedCount);
            
        }

        private void OnReceiveComplete(IAsyncResult ar)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProcForReceive), ar);            
        }

        private void OnSendComplete(IAsyncResult ar)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProcForSend), ar);            
        }

        private void OnDisconnect(IAsyncResult ar)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProcForDisconnect), ar);
            Console.WriteLine("OnDisconnect disconnctedCount {0}", ++_DisconnectedCount);
        }

        private void ThreadProcForAccept(Object stateInfo)
        {
            IAsyncResult ar = stateInfo as IAsyncResult;
            Session session = ar.AsyncState as Session;
            session._Client = _Listener.EndAcceptSocket(ar);
            session._Client.BeginReceive(session._ReceiveBuffer, 0, _PolicyRequestString.Length, SocketFlags.None,
                new AsyncCallback(OnReceiveComplete), ar.AsyncState);            
        }

        private void ThreadProcForDisconnect(Object stateInfo)
        {
            IAsyncResult ar = stateInfo as IAsyncResult;
            // Complete the disconnect request.
            Session session = ar.AsyncState as Session;
            session._Client.EndDisconnect(ar);
            session._Client.Close();
        }

        private void ThreadProcForSend(Object stateInfo)
        {
            IAsyncResult ar = stateInfo as IAsyncResult;
            Session session = ar.AsyncState as Session;
            try
            {
                int sendedLen = session._Client.EndSend(ar);
                if (sendedLen == 0)
                {
                    throw new SocketException((int)SocketError.Shutdown);
                }
                session._SendedLength += sendedLen;
                //See if there's more data that we need to grab
                if (session._SendedLength < _Policy.Length)
                {
                    //Need to grab more data so receive remaining data
                    session._Client.BeginSend(_Policy, session._SendedLength, _Policy.Length - session._SendedLength, SocketFlags.None, new AsyncCallback(OnSendComplete), ar.AsyncState);
                    return;
                }

                session.Disconnect(new AsyncCallback(OnDisconnect));
            }
            catch (Exception exp)
            {
                LogError(exp);
                session.Disconnect(new AsyncCallback(OnDisconnect));
            }           
        }

        private void ThreadProcForReceive(Object stateInfo)
        {
            IAsyncResult ar = stateInfo as IAsyncResult;
            Session session = ar.AsyncState as Session;
            try
            {
                int receivedLen = session._Client.EndReceive(ar);
                
                session._ReceivedLength += receivedLen;
                if (receivedLen == 0)
                {
                    throw new SocketException((int)SocketError.Shutdown);
                }

                //See if there's more data that we need to grab
                if (session._ReceivedLength < _PolicyRequestString.Length)
                {
                    //Need to grab more data so receive remaining data
                    session._Client.BeginReceive(session._ReceiveBuffer, session._ReceivedLength,
                        _PolicyRequestString.Length - session._ReceivedLength,
                        SocketFlags.None, new AsyncCallback(OnReceiveComplete), ar.AsyncState);
                    return;
                }

                //Check that <policy-file-request/> was sent from client
                string request = System.Text.Encoding.UTF8.GetString(session._ReceiveBuffer, 0, session._ReceivedLength);
                if (StringComparer.InvariantCultureIgnoreCase.Compare(request, _PolicyRequestString) != 0)
                {
                    //Data received isn't valid so close
                    session.Disconnect(new AsyncCallback(OnDisconnect));
                    return;
                }
                //Valid request received....send policy file
                session._Client.BeginSend(_Policy, 0, _Policy.Length, SocketFlags.None,
                    new AsyncCallback(OnSendComplete), ar.AsyncState);
            }
            catch (Exception exp)
            {
                LogError(exp);
                session.Disconnect(new AsyncCallback(OnDisconnect));
            }   
          
        }

        private void LogError(Exception exp)
        {
            string appFullPath = Assembly.GetCallingAssembly().Location;
            string logPath = appFullPath.Substring(0, appFullPath.LastIndexOf("\\")) + ".log";
            StreamWriter writer = new StreamWriter(logPath, true);
            try
            {
                writer.WriteLine(String.Format("Error in PolicySocketServer: "
                    + "{0} \r\n StackTrace: {1}", exp.Message, exp.StackTrace));
            }
            catch { }
            finally
            {
                writer.Close();
            }
        }
    }
}
